package top.magicpotato.fast.quartz.annotation;

import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.StringUtils;
import top.magicpotato.fast.quartz.job.StdJob;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;

/**
 * @author ycl
 * 用来 获取并处理 {@link QuartzScheduled} 标记的方法 放入调度器中
 */
public class QuartzBeanPostProcessor implements BeanPostProcessor {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private Scheduler scheduler;

    public QuartzBeanPostProcessor(Scheduler scheduler) {
        this.scheduler = scheduler;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
                bean instanceof ScheduledExecutorService) {
            // Ignore AOP infrastructure such as scoped proxies.
            return bean;
        }
        // 如果是代理类 则获取真实类
        Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
        // 排除不可能存在 QuartzScheduled 注解的类
        if (!AnnotationUtils.isCandidateClass(targetClass, Collections.singleton(QuartzScheduled.class))) {
            return bean;
        }
        // 在类上寻找包含注解的方法
        Map<Method, QuartzScheduled> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
                (MethodIntrospector.MetadataLookup<QuartzScheduled>) method -> AnnotationUtils.findAnnotation(method, QuartzScheduled.class));
        if (annotatedMethods.isEmpty()) {
            return bean;
        }
        String className = targetClass.getName();
        // 循环找到的方法，加入定时计划
        annotatedMethods.forEach((method, anno) -> {
            // 获取任务名字、组
            String name = StringUtils.hasLength(anno.name()) ? anno.name() : method.getName();
            String group = StringUtils.hasLength(anno.group()) ? anno.group() : className;

            // 创建job
            JobDetail jobDetail = JobBuilder.newJob(StdJob.class)
                    .withIdentity(name, group)
                    .usingJobData("beanName", beanName)
                    .usingJobData("className", className)
                    .usingJobData("methodName", method.getName())
                    .build();

            // 解析调度表达式
            CronScheduleBuilder cronSchedule = CronScheduleBuilder.cronSchedule(anno.cron());
            switch (anno.misfire()) {
                case withMisfireHandlingInstructionDoNothing:
                    cronSchedule.withMisfireHandlingInstructionDoNothing();
                    break;
                case withMisfireHandlingInstructionFireAndProceed:
                    cronSchedule.withMisfireHandlingInstructionFireAndProceed();
                    break;
                case withMisfireHandlingInstructionIgnoreMisfires:
                    cronSchedule.withMisfireHandlingInstructionIgnoreMisfires();
                    break;
                default:
                    logger.debug("采用默认补救策略");
            }

            // 创建触发器
            Trigger trigger = TriggerBuilder.newTrigger()
                    // 使用方法完全限定名当作 id，以便发现重复任务
                    .withIdentity(name, group)
                    .withSchedule(cronSchedule)
                    .build();
            try {
                if (!scheduler.checkExists(jobDetail.getKey())) {
                    // 任务不存在才进行加入任务
                    scheduler.scheduleJob(jobDetail, trigger);
                } else {
                    logger.error("任务{}已被其他节点创建", jobDetail.getKey().getName());
                }

            } catch (SchedulerException e) {
                e.printStackTrace();
            }
        });
        return bean;
    }
}
